// include requirements
include(jslib_io);
include(jslib_socket);
include(jslib_fileutils);
include('chrome://technika/content/libs/tstore/tstore.js');
include('chrome://technika/content/libs/tstore/tutils.js');

// extend a object
function TH_extend(target, obj) {
	for (var key in obj)
		target[key] = obj[key];
		
	return target;
}

// get url protocol
function TH_getURLScheme(url) {
	return Components.classes["@mozilla.org/network/io-service;1"]
		.getService(Components.interfaces.nsIIOService)
		.extractScheme(url);	
}

// get data store
function TH_getDataStore(name) {
	var fu = new FileUtils;
	var storeDir = fu.chromeToPath('chrome://technika/content/stores/');
	var storeFile = fu.append(storeDir, name);

	if (!fu.exists(storeFile))
		fu.create(storeFile);
	
	return new TFile(storeFile);
}

// delete data store
function TH_delDataStore(name) {
	var fu = new FileUtils;
	var storeDir = fu.chromeToPath('chrome://technika/content/stores/');
	var storeFile = fu.append(storeDir, name);

	if (fu.exists(storeFile))
		return fu.remove(storeFile);

	return false;
}

// delete any data store but system
function TH_delDataStoreSafe(name) {
	if (name == 'system' || name == 'session')
		return false;

	return TH_delDataStore(name);
}

// get data store listing
function TH_getDataStoreList() {
	var fu = new FileUtils;
	var storeDir = fu.chromeToPath('chrome://technika/content/stores/');
	var dir = new Dir(storeDir);

	var stores = [];
	var list = dir.readDir();

	for (var i = 0; i < list.length; i++)
		if (list[i].isFile())
			stores.push(fu.leaf(list[i].path));

	return stores;
}

// parse script
function TH_parseScript(source) {
	var script = {url: null, path: null, name: null, version: null, description: null, active: 'true', enabled: 'false', modified: null, source: source};
	var meta = source.match(/\@(\S+)\s+([^\n]+)/g);

	for (var i = 0; i < meta.length; i++) {
		var pair = meta[i].match(/\@(\S+)\s+(.*)\s*/);
		script[pair[1]] = pair[2];
	}

	if (!script.name || script.name.replace(/(^\s+|\s+$)/, '') == '')
		throw 'incorrect format';

	return script;
}

// repath script
function TH_repathScript(script) {
	var script = script;

	var fu = new FileUtils;
	var scriptDir = fu.chromeToPath('chrome://technika/content/scripts/');
	var fileName = script.name.replace(/(\s+|\W+)/g, '_').replace(/_+/g, '_').toLowerCase() + '.js';

	script.path = fu.append(scriptDir, fileName);
	script.url = 'chrome://technika/content/scripts/' + fileName;

	return script;
}

// create script
function TH_createScript(script) {
	var script = TH_repathScript(script);
	var system = TH_getDataStore('system');

	if (system.find({subject: 'urn:technika:scripts', predicate: 'hasScript', object: script.url}).subject)
		throw 'script exists';

	var file = new File(script.path);
	file.open('w');
	file.write(script.source);
	file.close();

	var fu = new FileUtils;
	var modified = fu.dateModified(script.path);

	system.insert({subject: 'urn:technika:scripts', predicate: 'hasScript', object: script.url});
	system.insert({subject: script.url, predicate: 'hasName', object: script.name});
	system.insert({subject: script.url, predicate: 'hasVersion', object: script.version});
	system.insert({subject: script.url, predicate: 'hasDescription', object: script.description});
	system.insert({subject: script.url, predicate: 'isActive', object: (script.active)?script.active:'true'});
	system.insert({subject: script.url, predicate: 'isEnabled', object: (script.enabled)?script.enabled:'false'});
	system.insert({subject: script.url, predicate: 'wasModified', object: modified});
	system.save();
}

// update script
function TH_updateScript(oldScript, newScript) {
	newScript.enabled = oldScript.enabled;
	
	TH_removeScript(oldScript);
	TH_createScript(newScript);
}

// remove script
function TH_removeScript(script) {
	TH_removeScriptSafe(script);

	var fu = new FileUtils;
	if (fu.exists(script.path))
		return fu.remove(script.path);
}

// remove script safe
function TH_removeScriptSafe(script) {
	if (script.name.toLowerCase() == 'system')
		return;

	var system = TH_getDataStore('system');
	system.remove({subject: script.url});
	system.remove({object: script.url});
	system.save();
}

// get script by URL
function TH_getScript(url) {
	var fu = new FileUtils;
	var store = TH_getDataStore('system');

	var path = fu.chromeToPath(url);
	var name = store.find({subject: url, predicate: 'hasName'}).object;
	var version = store.find({subject: url, predicate: 'hasVersion'}).object;
	var description = store.find({subject: url, predicate: 'hasDescription'}).object;
	var enabled = store.find({subject: url, predicate: 'isEnabled'}).object;
	var active = store.find({subject: url, predicate: 'isActive'}).object;
	var modified = store.find({subject: url, predicate: 'wasModified'}).object;
	
	if (!modified) {
		modified = fu.dateModified(path);
		
		store.insert({subject: url, predicate: 'wasModified', object: modified});
		store.save();
	}

	var file = new File(path);
	file.open();

	var source = file.read();

	file.close();

	return {url: url, path: path, name: name, version: version, description: description, enabled: enabled, active: active, modified: modified, source: source};
}

// get script listing
function TH_getScriptList() {
	var scripts = [];

	var store = TH_getDataStore('system');
	var scriptEntries = store.search({subject: 'urn:technika:scripts', predicate: 'hasScript'});

	for (var i = 0; i < scriptEntries.length; i++)
		scripts.push(TH_getScript(scriptEntries[i].object));

	return scripts;
}

// enable script
function TH_enableScript(script) {
	var system = TH_getDataStore('system');
	system.update(
		{subject: script.url, predicate: 'isEnabled', object: 'false'},
		{object: 'true'});
	system.save();
}

// disable script
function TH_disableScript(script) {
	var system = TH_getDataStore('system');
	system.update(
		{subject: script.url, predicate: 'isEnabled', object: 'true'},
		{object: 'false'});
	system.save();
}

// enable all scripts
function TH_enableAllScripts() {
	var system = TH_getDataStore('system');
	
	var scripts = TH_getInfo('scripts');
	for (var i = 0; i < scripts.length; i++)
		system.update(
			{subject: scripts[i].url, predicate: 'isEnabled', object: 'false'},
			{object: 'true'});

	system.save();
}

// disable all scripts
function TH_disableAllScripts() {
	var system = TH_getDataStore('system');
	
	var scripts = TH_getInfo('scripts');
	for (var i = 0; i < scripts.length; i++)
		system.update(
			{subject: scripts[i].url, predicate: 'isEnabled', object: 'true'},
			{object: 'false'});

	system.save();
}

// get system information
function TH_getInfo(key) {
	switch (key) {
		case 'stores':
			return TH_getDataStoreList();

		case 'scripts':
			return TH_getScriptList();
	}
}

// get main window
function TH_getMainWindow() {
	return window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
		.getInterface(Components.interfaces.nsIWebNavigation)
		.QueryInterface(Components.interfaces.nsIDocShellTreeItem)
		.rootTreeItem
		.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
		.getInterface(Components.interfaces.nsIDOMWindow);
}

// get selected browser
function TH_getSelectedBrowser() {
	var win = TH_getMainWindow();
	return win.gBrowser.selectedBrowser;
}

// get selected browser window
function TH_getSelectedBrowserWindow() {
	var win = TH_getMainWindow();
	return win.gBrowser.selectedBrowser.contentWindow;
}

// get selected browser document
function TH_getSelectedBrowserDocument() {
	var win = TH_getMainWindow();
	return win.gBrowser.selectedBrowser.contentDocument;
}

// get sandbox
function TH_getSandbox(win) {
	var sandbox = new Components.utils.Sandbox(win);

	TH_extend(sandbox, {
		window: win,
		document: win.document,

		include: include,

		Socket: Socket,
		XMLHttpRequest: XMLHttpRequest,

		$: {
			lib_jquery: 'chrome://technika/content/libs/jquery/jquery.js',
			lib_prototype: 'chrome://technika/content/libs/prototype/prototype.js',
			lib_tstore: 'chrome://technika/content/libs/tstore/tstore.js',
			lib_tstore_utils: 'chrome://technika/content/libs/tstore/tutils.js',

			info: TH_getInfo,
			store: TH_getDataStore,

			log: function (obj) {
				if (win.console && win.console.log)
					win.console.log('technika: ' + obj);
			},

			css: function (css) {
				var head = win.document.getElementsByTagName('head')[0];

				if (!head)
					return;

				var style = win.document.createElement('style');
				style.type = 'text/css';
				style.innerHTML = css;

				head.appendChild(style);
			},

			style: function (url) {
				var head = win.document.getElementsByTagName('head')[0];

				if (!head)
					return;

				link = win.document.createElement('link');
				link.rel = 'stylesheet';
				link.type = 'text/css';
				link.href = url;

				head.appendChild(link);
			},

			code: function(source) {
				var head = win.document.getElementsByTagName('head')[0];

				if (!head)
					return;

				script = win.document.createElement('script');
				script.defer = true;
				script.type = 'text/javascript';
				script.innerHTML = source;

				head.appendChild(script);
			},

			script: function (url) {
				var head = win.document.getElementsByTagName('head')[0];

				if (!head)
					return;

				script = win.document.createElement('script');
				script.defer = true;
				script.type = 'text/javascript';
				script.src = url;

				head.appendChild(script);
			},

			open: function (url) {
				
			},

			socket: function (host, port, binary) {
				var socket = new Socket;
				socket.open(host, port, binary);

				var start = new Date().getTime();
				var end   = new Date().getTime();

				while (socket.available() == 0 && (end - start) < 3000)
					end = new Date().getTime();

				return socket;
			},

			xhr: function (request) {
				var ontimeout = (typeof request.ontimeout == 'function')?request.ontimeout:function(){};
				var onerror = (typeof request.onerror == 'function')?request.onerror:function(){};
				var onload = (typeof request.onload == 'function')?request.onload:function(){};

				var tmr = null;
				var xhr = new XMLHttpRequest();

				if (!xhr) {
					onerror('request implementation not found', request);
					return;
				}

				xhr.onreadystatechange = function () {
					if (xhr.readyState == 4) {
						window.clearTimeout(tmr);

						var tokens = xhr.getAllResponseHeaders().split(/([^:]+):(.*)/);
						var headers = {};

						for (var i = 0; i < tokens.length; i += 3) {
							if (!tokens[i + 1])
								continue;

							var header = tokens[i + 1].replace(/(^\s+|\s+$)/g, '');
							var value = tokens[i + 2].replace(/(^\s+|\s+$)/g, '');

							headers[header] = value;
						}

						onload({status: xhr.status, data: xhr.responseText, headers: headers}, request);
					}
				};

				try {
					xhr.open(request.method?request.method:'GET', request.url);

					if (request.headers)
						for (var header in request.headers)
							xhr.setRequestHeader(header, request.headers[header]);

					xhr.send(request.body?request.body:(request.query?AttackAPI.query(request.query):null));
				} catch (e) {
					onerror(e, request);
					return;
				}

				tmr = win.setTimeout(function () {
					xhr.abort();
					request.ontimeout(request);
				}, request.timeout?request.timeout:10000);
			},

			run: function (program, args) {
				var fu = new FileUtils;
				fu.run(program, (args != undefined)?args:[]);
			},
		}
	});

	return sandbox;
}

// execute event if scheme allowed
function TH_ifSchemeAllowed(func) {
	var win = TH_getSelectedBrowserWindow();
	var scheme = TH_getURLScheme(win.location);

	if (scheme == 'http' || scheme == 'https' || scheme == 'ftp' || scheme == 'file' || scheme == 'data')
		func(scheme, win);
	else
		throw 'scheme not supported';
}

// eval JavaScript in sandbox
function TH_evalInSandbox(code, codebase, sandbox) {
	if (Components.utils && Components.utils.Sandbox)
		Components.utils.evalInSandbox(code, sandbox);
	else if (Components.utils && Components.utils.evalInSandbox)
		Components.utils.evalInSandbox(code, codebase, sandbox);
	else if (Sandbox)
		evalInSandbox(code, sandbox, codebase);
	else
		throw 'cannot eval';
}

// inject script extended
function TH_injectScript(script, codebase, sandbox, safeWindow) {
	try {
		TH_evalInSandbox('(function(){' + script.source + '})()', codebase, sandbox);
	} catch (e) {
		var e = new Error((typeof e == 'string')?e:e.message);
		e.fileName = script.name;
		e.lineNumber = 0;

		alert(e);
	}
}

// inject script default
function TH_injectScriptDefault(script) {
	TH_ifSchemeAllowed(function (scheme, win) {
		var unsafeWindow = win;

		if (unsafeWindow.wrappedJSObject)
			unsafeWindow = unsafeWindow.wrappedJSObject;

		TH_injectScript(script, unsafeWindow.location.href, TH_getSandbox(unsafeWindow), unsafeWindow);
	});
}

// inject scripts default
function TH_injectScriptsDefault(scripts) {
	TH_ifSchemeAllowed(function (scheme, win) {
		var unsafeWindow = win;

		if (unsafeWindow.wrappedJSObject)
			unsafeWindow = unsafeWindow.wrappedJSObject;

		for (var i = 0; i < scripts.length; i++)
			TH_injectScript(scripts[i], unsafeWindow.location.href, TH_getSandbox(unsafeWindow), unsafeWindow);
	});
}

// tfile helper class
function TFile(path) {
	this.path = path;
	this.store = new TStore;

	this.load();
}

TFile.prototype.load = function (path) {
	var path = (path == undefined)?this.path:path;

	var file = new File(path);
	file.open();

	var store = TDeserializer(file.read());

	file.close();
	this.store.inject(store);
};

TFile.prototype.save = function (path) {
	var path = (path == undefined)?this.path:path;

	var file = new File(path);
	file.open('w');
	file.write(TSerializer(this.store));
	file.close();
};

TFile.prototype.insert = function (t) { return this.store.insert(t) };
TFile.prototype.remove = function (t) { return this.store.remove(t) };
TFile.prototype.update = function (a, b) { return this.store.update(a, b) };
TFile.prototype.inject = function (t) { return this.store.inject(t) };
TFile.prototype.shrink = function (t) { return this.store.shrink(t) };
TFile.prototype.search = function (t) { return this.store.search(t) };
TFile.prototype.find = function (t) { return this.store.find(t) };